I learned a new standard library thing today
Got up early again...
Nvim Homework
I started using "go to definition" and "go to reference" a lot more. The amount of ways to navigate that are just part of my vocabulary now is increasing.
Diagnostics listing only shows problems in current file
I went and read up on how rust-analyzer
can be configured, but didn't find anything obvious. But I did find out about Workspaces a bit, and the fact that I can fuzzy find symbols in my current workspace. Pretty cool.
Despite fixing my LSP configuration a little bit (I was technically using clippy
wrong), the core problem remains open.
LSP action rename
doesn't save files
Unsolved; but I'll stop bringing it up until it becomes relevant.
The task itself
Doing part 1 for the first time, I did a linear scan through the entire map, and whenever I found a number, check if there's a symbol in the rectangle around it. It worked to get the answer.
However, upon seeing part two, my golfing brain activated, and I double checked my puzzle input to make sure that there is only ever up to 1 symbol adjacent to any given number. Which means that finding all symbols and then summing up adjacent numbers would work just as well, and not lead to double counting anything.
This is relevant, because we have to search from symbol side anyway for part 2. Thus I was able to factor out a majority of the solution to a common base function:
pub fn one(input: &str) -> crate::Result<i32> {
let is_symbol = |c: u8| !c.is_ascii_digit() && c != b'.';
adjacent_number_pair_sum(input, is_symbol, |(n1, n2)| n1 + n2)
}
pub fn two(input: &str) -> crate::Result<i32> {
adjacent_number_pair_sum(input, |c| c == b'*', |(n1, n2)| n1 * n2)
}
I'm not quite happy with the name of the base function, but it's doing a pretty gnarly thing overall, so eh. I wrote comments that should be clear enough.
Also, since I'm operating on bytes instead of a string, like so:
let map: Vec<&[u8]> = input.lines().map(|line| line.as_bytes()).collect();
I had the issue of actually parsing a number once I found it. Rust only lets you call .parse()
on actual strings pretty much. My first, original approach was this:
// given a &[u8] subslice calculated earlier
let num = String::from_utf8(subslice.to_vec()).ok()?;
let num = num.parse::<i32>().ok()?;
It bothered me though, because I was allocating a new Vec<u8>
for no good reason. A brief search told me that there is a better way, with pretty much the exact same API:
// given a &[u8] subslice calculated earlier
let num = std::str::from_utf8(subslice).ok()?;
let num = num.parse::<i32>().ok()?;
Because &str
doesn't own its data, we can simply project the byte array as a &str
without allocating anything! Doesn't seem like much, but it made me happy.